{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "from sklearn.utils import shuffle\n",
    "import re\n",
    "import time\n",
    "import collections\n",
    "import os"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def build_dataset(words, n_words, atleast=1):\n",
    "    count = [['PAD', 0], ['GO', 1], ['EOS', 2], ['UNK', 3]]\n",
    "    counter = collections.Counter(words).most_common(n_words)\n",
    "    counter = [i for i in counter if i[1] >= atleast]\n",
    "    count.extend(counter)\n",
    "    dictionary = dict()\n",
    "    for word, _ in count:\n",
    "        dictionary[word] = len(dictionary)\n",
    "    data = list()\n",
    "    unk_count = 0\n",
    "    for word in words:\n",
    "        index = dictionary.get(word, 0)\n",
    "        if index == 0:\n",
    "            unk_count += 1\n",
    "        data.append(index)\n",
    "    count[0][1] = unk_count\n",
    "    reversed_dictionary = dict(zip(dictionary.values(), dictionary.keys()))\n",
    "    return data, count, dictionary, reversed_dictionary"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "lines = open('movie_lines.txt', encoding='utf-8', errors='ignore').read().split('\\n')\n",
    "conv_lines = open('movie_conversations.txt', encoding='utf-8', errors='ignore').read().split('\\n')\n",
    "\n",
    "id2line = {}\n",
    "for line in lines:\n",
    "    _line = line.split(' +++$+++ ')\n",
    "    if len(_line) == 5:\n",
    "        id2line[_line[0]] = _line[4]\n",
    "        \n",
    "convs = [ ]\n",
    "for line in conv_lines[:-1]:\n",
    "    _line = line.split(' +++$+++ ')[-1][1:-1].replace(\"'\",\"\").replace(\" \",\"\")\n",
    "    convs.append(_line.split(','))\n",
    "    \n",
    "questions = []\n",
    "answers = []\n",
    "\n",
    "for conv in convs:\n",
    "    for i in range(len(conv)-1):\n",
    "        questions.append(id2line[conv[i]])\n",
    "        answers.append(id2line[conv[i+1]])\n",
    "        \n",
    "def clean_text(text):\n",
    "    text = text.lower()\n",
    "    text = re.sub(r\"i'm\", \"i am\", text)\n",
    "    text = re.sub(r\"he's\", \"he is\", text)\n",
    "    text = re.sub(r\"she's\", \"she is\", text)\n",
    "    text = re.sub(r\"it's\", \"it is\", text)\n",
    "    text = re.sub(r\"that's\", \"that is\", text)\n",
    "    text = re.sub(r\"what's\", \"that is\", text)\n",
    "    text = re.sub(r\"where's\", \"where is\", text)\n",
    "    text = re.sub(r\"how's\", \"how is\", text)\n",
    "    text = re.sub(r\"\\'ll\", \" will\", text)\n",
    "    text = re.sub(r\"\\'ve\", \" have\", text)\n",
    "    text = re.sub(r\"\\'re\", \" are\", text)\n",
    "    text = re.sub(r\"\\'d\", \" would\", text)\n",
    "    text = re.sub(r\"\\'re\", \" are\", text)\n",
    "    text = re.sub(r\"won't\", \"will not\", text)\n",
    "    text = re.sub(r\"can't\", \"cannot\", text)\n",
    "    text = re.sub(r\"n't\", \" not\", text)\n",
    "    text = re.sub(r\"n'\", \"ng\", text)\n",
    "    text = re.sub(r\"'bout\", \"about\", text)\n",
    "    text = re.sub(r\"'til\", \"until\", text)\n",
    "    text = re.sub(r\"[-()\\\"#/@;:<>{}`+=~|.!?,]\", \"\", text)\n",
    "    return ' '.join([i.strip() for i in filter(None, text.split())])\n",
    "\n",
    "clean_questions = []\n",
    "for question in questions:\n",
    "    clean_questions.append(clean_text(question))\n",
    "    \n",
    "clean_answers = []    \n",
    "for answer in answers:\n",
    "    clean_answers.append(clean_text(answer))\n",
    "    \n",
    "min_line_length = 2\n",
    "max_line_length = 5\n",
    "short_questions_temp = []\n",
    "short_answers_temp = []\n",
    "\n",
    "i = 0\n",
    "for question in clean_questions:\n",
    "    if len(question.split()) >= min_line_length and len(question.split()) <= max_line_length:\n",
    "        short_questions_temp.append(question)\n",
    "        short_answers_temp.append(clean_answers[i])\n",
    "    i += 1\n",
    "\n",
    "short_questions = []\n",
    "short_answers = []\n",
    "\n",
    "i = 0\n",
    "for answer in short_answers_temp:\n",
    "    if len(answer.split()) >= min_line_length and len(answer.split()) <= max_line_length:\n",
    "        short_answers.append(answer)\n",
    "        short_questions.append(short_questions_temp[i])\n",
    "    i += 1\n",
    "    \n",
    "question_test = short_questions[500:550]\n",
    "answer_test = short_answers[500:550]\n",
    "short_questions = short_questions[:500]\n",
    "short_answers = short_answers[:500]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "vocab from size: 657\n",
      "Most common words [('you', 132), ('is', 78), ('i', 68), ('what', 51), ('it', 50), ('that', 49)]\n",
      "Sample data [7, 28, 129, 35, 61, 42, 12, 22, 82, 225] ['what', 'good', 'stuff', 'she', 'okay', 'they', 'do', 'to', 'hey', 'sweet']\n",
      "filtered vocab size: 661\n",
      "% of vocab used: 100.61%\n"
     ]
    }
   ],
   "source": [
    "concat_from = ' '.join(short_questions+question_test).split()\n",
    "vocabulary_size_from = len(list(set(concat_from)))\n",
    "data_from, count_from, dictionary_from, rev_dictionary_from = build_dataset(concat_from, vocabulary_size_from)\n",
    "print('vocab from size: %d'%(vocabulary_size_from))\n",
    "print('Most common words', count_from[4:10])\n",
    "print('Sample data', data_from[:10], [rev_dictionary_from[i] for i in data_from[:10]])\n",
    "print('filtered vocab size:',len(dictionary_from))\n",
    "print(\"% of vocab used: {}%\".format(round(len(dictionary_from)/vocabulary_size_from,4)*100))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "vocab from size: 660\n",
      "Most common words [('i', 97), ('you', 91), ('is', 62), ('it', 58), ('not', 47), ('what', 39)]\n",
      "Sample data [12, 216, 5, 4, 94, 25, 59, 10, 8, 79] ['the', 'real', 'you', 'i', 'hope', 'so', 'they', 'do', 'not', 'hi']\n",
      "filtered vocab size: 664\n",
      "% of vocab used: 100.61%\n"
     ]
    }
   ],
   "source": [
    "concat_to = ' '.join(short_answers+answer_test).split()\n",
    "vocabulary_size_to = len(list(set(concat_to)))\n",
    "data_to, count_to, dictionary_to, rev_dictionary_to = build_dataset(concat_to, vocabulary_size_to)\n",
    "print('vocab from size: %d'%(vocabulary_size_to))\n",
    "print('Most common words', count_to[4:10])\n",
    "print('Sample data', data_to[:10], [rev_dictionary_to[i] for i in data_to[:10]])\n",
    "print('filtered vocab size:',len(dictionary_to))\n",
    "print(\"% of vocab used: {}%\".format(round(len(dictionary_to)/vocabulary_size_to,4)*100))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "GO = dictionary_from['GO']\n",
    "PAD = dictionary_from['PAD']\n",
    "EOS = dictionary_from['EOS']\n",
    "UNK = dictionary_from['UNK']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "for i in range(len(short_answers)):\n",
    "    short_answers[i] += ' EOS'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "def layer_norm(inputs, epsilon=1e-8):\n",
    "    mean, variance = tf.nn.moments(inputs, [-1], keep_dims=True)\n",
    "    normalized = (inputs - mean) / (tf.sqrt(variance + epsilon))\n",
    "\n",
    "    params_shape = inputs.get_shape()[-1:]\n",
    "    gamma = tf.get_variable('gamma', params_shape, tf.float32, tf.ones_initializer())\n",
    "    beta = tf.get_variable('beta', params_shape, tf.float32, tf.zeros_initializer())\n",
    "    \n",
    "    outputs = gamma * normalized + beta\n",
    "    return outputs\n",
    "\n",
    "\n",
    "def multihead_attn(queries, keys, q_masks, k_masks, future_binding, num_units, num_heads):\n",
    "    \n",
    "    T_q = tf.shape(queries)[1]                                      \n",
    "    T_k = tf.shape(keys)[1]                  \n",
    "\n",
    "    Q = tf.layers.dense(queries, num_units, name='Q')                              \n",
    "    K_V = tf.layers.dense(keys, 2*num_units, name='K_V')    \n",
    "    K, V = tf.split(K_V, 2, -1)        \n",
    "\n",
    "    Q_ = tf.concat(tf.split(Q, num_heads, axis=2), axis=0)                         \n",
    "    K_ = tf.concat(tf.split(K, num_heads, axis=2), axis=0)                    \n",
    "    V_ = tf.concat(tf.split(V, num_heads, axis=2), axis=0)                      \n",
    "\n",
    "    align = tf.matmul(Q_, tf.transpose(K_, [0,2,1]))                      \n",
    "    align = align / np.sqrt(K_.get_shape().as_list()[-1])                 \n",
    "\n",
    "    paddings = tf.fill(tf.shape(align), float('-inf'))                   \n",
    "\n",
    "    key_masks = k_masks                                                 \n",
    "    key_masks = tf.tile(key_masks, [num_heads, 1])                       \n",
    "    key_masks = tf.tile(tf.expand_dims(key_masks, 1), [1, T_q, 1])            \n",
    "    align = tf.where(tf.equal(key_masks, 0), paddings, align)       \n",
    "\n",
    "    if future_binding:\n",
    "        lower_tri = tf.ones([T_q, T_k])                                          \n",
    "        lower_tri = tf.linalg.LinearOperatorLowerTriangular(lower_tri).to_dense()  \n",
    "        masks = tf.tile(tf.expand_dims(lower_tri,0), [tf.shape(align)[0], 1, 1]) \n",
    "        align = tf.where(tf.equal(masks, 0), paddings, align)                      \n",
    "    \n",
    "    align = tf.nn.softmax(align)                                            \n",
    "    query_masks = tf.to_float(q_masks)                                             \n",
    "    query_masks = tf.tile(query_masks, [num_heads, 1])                             \n",
    "    query_masks = tf.tile(tf.expand_dims(query_masks, -1), [1, 1, T_k])            \n",
    "    align *= query_masks                                                           \n",
    "          \n",
    "    outputs = tf.matmul(align, V_)                                                 \n",
    "    outputs = tf.concat(tf.split(outputs, num_heads, axis=0), axis=2)             \n",
    "    outputs += queries                                                             \n",
    "    outputs = layer_norm(outputs)                                                 \n",
    "    return outputs\n",
    "\n",
    "\n",
    "def pointwise_feedforward(inputs, hidden_units, activation=None):\n",
    "    outputs = tf.layers.dense(inputs, 4*hidden_units, activation=activation)\n",
    "    outputs = tf.layers.dense(outputs, hidden_units, activation=None)\n",
    "    outputs += inputs\n",
    "    outputs = layer_norm(outputs)\n",
    "    return outputs\n",
    "\n",
    "\n",
    "def learned_position_encoding(inputs, mask, embed_dim):\n",
    "    T = tf.shape(inputs)[1]\n",
    "    outputs = tf.range(tf.shape(inputs)[1])                # (T_q)\n",
    "    outputs = tf.expand_dims(outputs, 0)                   # (1, T_q)\n",
    "    outputs = tf.tile(outputs, [tf.shape(inputs)[0], 1])   # (N, T_q)\n",
    "    outputs = embed_seq(outputs, T, embed_dim, zero_pad=False, scale=False)\n",
    "    return tf.expand_dims(tf.to_float(mask), -1) * outputs\n",
    "\n",
    "\n",
    "def sinusoidal_position_encoding(inputs, mask, repr_dim):\n",
    "    T = tf.shape(inputs)[1]\n",
    "    pos = tf.reshape(tf.range(0.0, tf.to_float(T), dtype=tf.float32), [-1, 1])\n",
    "    i = np.arange(0, repr_dim, 2, np.float32)\n",
    "    denom = np.reshape(np.power(10000.0, i / repr_dim), [1, -1])\n",
    "    enc = tf.expand_dims(tf.concat([tf.sin(pos / denom), tf.cos(pos / denom)], 1), 0)\n",
    "    return tf.tile(enc, [tf.shape(inputs)[0], 1, 1]) * tf.expand_dims(tf.to_float(mask), -1)\n",
    "\n",
    "\n",
    "def label_smoothing(inputs, epsilon=0.1):\n",
    "    C = inputs.get_shape().as_list()[-1]\n",
    "    return ((1 - epsilon) * inputs) + (epsilon / C)\n",
    "\n",
    "\n",
    "class Chatbot:\n",
    "    def __init__(self, size_layer, embedded_size, from_dict_size, to_dict_size, learning_rate,\n",
    "                 num_blocks = 2,\n",
    "                 num_heads = 8,\n",
    "                 min_freq = 50):\n",
    "        self.X = tf.placeholder(tf.int32,[None,None])\n",
    "        self.Y = tf.placeholder(tf.int32,[None,None])\n",
    "        \n",
    "        self.X_seq_len = tf.count_nonzero(self.X, 1, dtype=tf.int32)\n",
    "        self.Y_seq_len = tf.count_nonzero(self.Y, 1, dtype=tf.int32)\n",
    "        batch_size = tf.shape(self.X)[0]\n",
    "        \n",
    "        encoder_embedding = tf.Variable(tf.random_uniform([from_dict_size, embedded_size], -1, 1))\n",
    "        decoder_embedding = tf.Variable(tf.random_uniform([to_dict_size, embedded_size], -1, 1))\n",
    "        \n",
    "        main = tf.strided_slice(self.Y, [0, 0], [batch_size, -1], [1, 1])\n",
    "        decoder_input = tf.concat([tf.fill([batch_size, 1], GO), main], 1)\n",
    "        \n",
    "        def forward(x, y):\n",
    "            encoder_embedded = tf.nn.embedding_lookup(encoder_embedding, x)\n",
    "            en_masks = tf.sign(x)\n",
    "            encoder_embedded += sinusoidal_position_encoding(x, en_masks, embedded_size)\n",
    "        \n",
    "            for i in range(num_blocks):\n",
    "                with tf.variable_scope('encoder_self_attn_%d'%i,reuse=tf.AUTO_REUSE):\n",
    "                    encoder_embedded = multihead_attn(queries = encoder_embedded,\n",
    "                                             keys = encoder_embedded,\n",
    "                                             q_masks = en_masks,\n",
    "                                             k_masks = en_masks,\n",
    "                                             future_binding = False,\n",
    "                                             num_units = size_layer,\n",
    "                                             num_heads = num_heads)\n",
    "\n",
    "                with tf.variable_scope('encoder_feedforward_%d'%i,reuse=tf.AUTO_REUSE):\n",
    "                    encoder_embedded = pointwise_feedforward(encoder_embedded,\n",
    "                                                    embedded_size,\n",
    "                                                    activation = tf.nn.relu)\n",
    "            \n",
    "            decoder_embedded = tf.nn.embedding_lookup(decoder_embedding, y)\n",
    "            de_masks = tf.sign(y)\n",
    "            decoder_embedded += sinusoidal_position_encoding(y, de_masks, embedded_size)\n",
    "            \n",
    "            for i in range(num_blocks):\n",
    "                with tf.variable_scope('decoder_self_attn_%d'%i,reuse=tf.AUTO_REUSE):\n",
    "                    decoder_embedded = multihead_attn(queries = decoder_embedded,\n",
    "                                         keys = decoder_embedded,\n",
    "                                         q_masks = de_masks,\n",
    "                                         k_masks = de_masks,\n",
    "                                         future_binding = True,\n",
    "                                         num_units = size_layer,\n",
    "                                         num_heads = num_heads)\n",
    "                \n",
    "                with tf.variable_scope('decoder_attn_%d'%i,reuse=tf.AUTO_REUSE):\n",
    "                    decoder_embedded = multihead_attn(queries = decoder_embedded,\n",
    "                                         keys = encoder_embedded,\n",
    "                                         q_masks = de_masks,\n",
    "                                         k_masks = en_masks,\n",
    "                                         future_binding = False,\n",
    "                                         num_units = size_layer,\n",
    "                                         num_heads = num_heads)\n",
    "                \n",
    "                with tf.variable_scope('decoder_feedforward_%d'%i,reuse=tf.AUTO_REUSE):\n",
    "                    decoder_embedded = pointwise_feedforward(decoder_embedded,\n",
    "                                                    embedded_size,\n",
    "                                            activation = tf.nn.relu)\n",
    "            \n",
    "            return tf.layers.dense(decoder_embedded, to_dict_size, reuse=tf.AUTO_REUSE)\n",
    "        \n",
    "        self.training_logits = forward(self.X, decoder_input)\n",
    "        \n",
    "        def cond(i, y, temp):\n",
    "            return i < 2 * tf.reduce_max(self.X_seq_len)\n",
    "        \n",
    "        def body(i, y, temp):\n",
    "            logits = forward(self.X, y)\n",
    "            ids = tf.argmax(logits, -1)[:, i]\n",
    "            ids = tf.expand_dims(ids, -1)\n",
    "            temp = tf.concat([temp[:, 1:], ids], -1)\n",
    "            y = tf.concat([temp[:, -(i+1):], temp[:, :-(i+1)]], -1)\n",
    "            y = tf.reshape(y, [tf.shape(temp)[0], 2 * tf.reduce_max(self.X_seq_len)])\n",
    "            i += 1\n",
    "            return i, y, temp\n",
    "        \n",
    "        target = tf.fill([batch_size, 2 * tf.reduce_max(self.X_seq_len)], GO)\n",
    "        target = tf.cast(target, tf.int64)\n",
    "        self.target = target\n",
    "        \n",
    "        _, self.predicting_ids, _ = tf.while_loop(cond, body, \n",
    "                                                  [tf.constant(0), target, target])\n",
    "        self.logits = forward(self.X, self.Y)\n",
    "        self.k = tf.placeholder(dtype = tf.int32)\n",
    "        p = tf.nn.softmax(self.logits)\n",
    "        self.topk_logprobs, self.topk_ids = tf.nn.top_k(tf.log(p), self.k)\n",
    "        \n",
    "        masks = tf.sequence_mask(self.Y_seq_len, tf.reduce_max(self.Y_seq_len), dtype=tf.float32)\n",
    "        self.cost = tf.contrib.seq2seq.sequence_loss(logits = self.training_logits,\n",
    "                                                     targets = self.Y,\n",
    "                                                     weights = masks)\n",
    "        self.optimizer = tf.train.AdamOptimizer(learning_rate = learning_rate).minimize(self.cost)\n",
    "        y_t = tf.argmax(self.training_logits,axis=2)\n",
    "        y_t = tf.cast(y_t, tf.int32)\n",
    "        self.prediction = tf.boolean_mask(y_t, masks)\n",
    "        mask_label = tf.boolean_mask(self.Y, masks)\n",
    "        correct_pred = tf.equal(self.prediction, mask_label)\n",
    "        correct_index = tf.cast(correct_pred, tf.float32)\n",
    "        self.accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "embedded_size = 256\n",
    "learning_rate = 0.001\n",
    "batch_size = 16\n",
    "epoch = 20"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "tf.reset_default_graph()\n",
    "sess = tf.InteractiveSession()\n",
    "model = Chatbot(embedded_size, embedded_size, len(dictionary_from), \n",
    "                len(dictionary_to), learning_rate)\n",
    "sess.run(tf.global_variables_initializer())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "def str_idx(corpus, dic):\n",
    "    X = []\n",
    "    for i in corpus:\n",
    "        ints = []\n",
    "        for k in i.split():\n",
    "            ints.append(dic.get(k,UNK))\n",
    "        X.append(ints)\n",
    "    return X"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "X = str_idx(short_questions, dictionary_from)\n",
    "Y = str_idx(short_answers, dictionary_to)\n",
    "X_test = str_idx(question_test, dictionary_from)\n",
    "Y_test = str_idx(answer_test, dictionary_from)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "def pad_sentence_batch(sentence_batch, pad_int):\n",
    "    padded_seqs = []\n",
    "    seq_lens = []\n",
    "    max_sentence_len = max([len(sentence) for sentence in sentence_batch])\n",
    "    for sentence in sentence_batch:\n",
    "        padded_seqs.append(sentence + [pad_int] * (max_sentence_len - len(sentence)))\n",
    "        seq_lens.append(len(sentence))\n",
    "    return padded_seqs, seq_lens"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch: 1, avg loss: 5.453336, avg accuracy: 0.214531\n",
      "epoch: 2, avg loss: 4.879088, avg accuracy: 0.264913\n",
      "epoch: 3, avg loss: 4.622193, avg accuracy: 0.282723\n",
      "epoch: 4, avg loss: 4.228430, avg accuracy: 0.298138\n",
      "epoch: 5, avg loss: 3.886595, avg accuracy: 0.313478\n",
      "epoch: 6, avg loss: 3.455858, avg accuracy: 0.355310\n",
      "epoch: 7, avg loss: 3.063944, avg accuracy: 0.410093\n",
      "epoch: 8, avg loss: 2.842969, avg accuracy: 0.452698\n",
      "epoch: 9, avg loss: 2.591064, avg accuracy: 0.479848\n",
      "epoch: 10, avg loss: 2.306369, avg accuracy: 0.545933\n",
      "epoch: 11, avg loss: 2.093652, avg accuracy: 0.583499\n",
      "epoch: 12, avg loss: 1.940059, avg accuracy: 0.611497\n",
      "epoch: 13, avg loss: 1.714187, avg accuracy: 0.656130\n",
      "epoch: 14, avg loss: 1.585506, avg accuracy: 0.674867\n",
      "epoch: 15, avg loss: 1.420781, avg accuracy: 0.708977\n",
      "epoch: 16, avg loss: 1.348889, avg accuracy: 0.713253\n",
      "epoch: 17, avg loss: 1.241284, avg accuracy: 0.744176\n",
      "epoch: 18, avg loss: 1.126200, avg accuracy: 0.755379\n",
      "epoch: 19, avg loss: 0.964989, avg accuracy: 0.782745\n",
      "epoch: 20, avg loss: 0.746517, avg accuracy: 0.836433\n"
     ]
    }
   ],
   "source": [
    "for i in range(epoch):\n",
    "    total_loss, total_accuracy = 0, 0\n",
    "    for k in range(0, len(short_questions), batch_size):\n",
    "        index = min(k+batch_size, len(short_questions))\n",
    "        batch_x, seq_x = pad_sentence_batch(X[k: index], PAD)\n",
    "        batch_y, seq_y = pad_sentence_batch(Y[k: index], PAD)\n",
    "        accuracy,loss, _ = sess.run([model.accuracy, model.cost, model.optimizer], \n",
    "                                      feed_dict={model.X:batch_x,\n",
    "                                                model.Y:batch_y})\n",
    "        total_loss += loss\n",
    "        total_accuracy += accuracy\n",
    "    total_loss /= (len(short_questions) / batch_size)\n",
    "    total_accuracy /= (len(short_questions) / batch_size)\n",
    "    print('epoch: %d, avg loss: %f, avg accuracy: %f'%(i+1, total_loss, total_accuracy))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Hypothesis:\n",
    "    def __init__(self, log_prob, seq):\n",
    "        self.log_prob = log_prob\n",
    "        self.seq = seq\n",
    "\n",
    "    @property\n",
    "    def step(self):\n",
    "        return len(self.seq) - 1\n",
    "\n",
    "\n",
    "def beam_search(\n",
    "    batch_x,\n",
    "    beam_size,\n",
    "    num_ans = 5,\n",
    "    normalize_by_len = 1.0,\n",
    "):\n",
    "    assert 0 <= normalize_by_len <= 1\n",
    "    batch_size = len(batch_x)\n",
    "    max_len = len(batch_x[0]) * 2\n",
    "    dec_inputs = np.ones((batch_size, 2), dtype=np.int32)\n",
    "    answers = [[] for i in range(batch_size)]\n",
    "    H = [[] for i in range(batch_size)]\n",
    "    \n",
    "    tkl, tkid = sess.run([model.topk_logprobs, \n",
    "                          model.topk_ids],\n",
    "                         feed_dict = {model.X: batch_x,\n",
    "                                     model.Y: dec_inputs,\n",
    "                                     model.k: beam_size})\n",
    "    for i in range(batch_size):\n",
    "        for j, log_prob in enumerate(tkl[i, 0]):\n",
    "            if tkid[i, 0, j] != EOS:\n",
    "                h = Hypothesis(log_prob, [1, tkid[i, 0, j]])\n",
    "                H[i].append(h)\n",
    "        H[i].sort(key=lambda h: h.log_prob)\n",
    "    \n",
    "    done = [False] * batch_size\n",
    "    while not all(done):\n",
    "        tkl_beam = []\n",
    "        tkid_beam = []\n",
    "        dec_inputs_beam = []\n",
    "        steps_beam = []\n",
    "        for i in range(beam_size):\n",
    "            steps = [1] * batch_size\n",
    "            prev_log_probs = np.zeros(batch_size, dtype=np.float32)\n",
    "            dec_inputs = np.ones((batch_size, max_len), dtype=np.int32)\n",
    "            for j, h in enumerate(H):\n",
    "                while h:\n",
    "                    hi = h.pop()\n",
    "                    lp, step, candidate_seq = hi.log_prob, hi.step, hi.seq\n",
    "                    if candidate_seq[-1] != EOS:\n",
    "                        dec_inputs[j, :len(candidate_seq)] = candidate_seq\n",
    "                        steps[j] = step\n",
    "                        prev_log_probs[j] = lp\n",
    "                        break\n",
    "                    else:\n",
    "                        answers[j].append((lp, candidate_seq))\n",
    "            max_step = max(steps)\n",
    "            dec_inputs = dec_inputs[:, :max_step + 2]\n",
    "            tkl, tkid = sess.run([model.topk_logprobs, \n",
    "                          model.topk_ids],\n",
    "                         feed_dict = {model.X: batch_x,\n",
    "                                     model.Y: dec_inputs,\n",
    "                                     model.k: beam_size})\n",
    "            \n",
    "            tkl_beam.append(tkl + prev_log_probs[:, None, None])\n",
    "            tkid_beam.append(tkid)\n",
    "            dec_inputs_beam.append(dec_inputs.copy())\n",
    "            steps_beam.append(steps)\n",
    "            \n",
    "        for i in range(beam_size):\n",
    "            tkl = tkl_beam[i]\n",
    "            tkid = tkid_beam[i]\n",
    "            dec_inputs = dec_inputs_beam[i]\n",
    "            steps = steps_beam[i]\n",
    "            for j in range(batch_size):\n",
    "                step = steps[j]\n",
    "                for k in range(tkid.shape[2]):\n",
    "                    extended_seq = np.hstack((dec_inputs[j, :step+1], [tkid[j, step, k]]))\n",
    "                    log_prob = tkl[j, step, k]\n",
    "                    if len(extended_seq) <= max_len and log_prob > -10:\n",
    "                        h = Hypothesis(log_prob, extended_seq)\n",
    "                        H[j].append(h)\n",
    "                H[j].sort(key=lambda h: h.log_prob / (h.step**normalize_by_len))\n",
    "            \n",
    "        for i in range(batch_size):\n",
    "            done[i] = (len(answers[i]) >= num_ans) or (not H[i]) or (len(H[i]) > 100)\n",
    "            \n",
    "    return answers    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "beamed = beam_search(batch_x, 5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(-0.077265516, array([  1,  11, 619,   2], dtype=int32)),\n",
       " (-5.1455, array([  1,  11, 508,   2], dtype=int32)),\n",
       " (-5.655004, array([  1,  11, 556,   2], dtype=int32)),\n",
       " (-5.846057, array([  1,  66,  11, 619,   2], dtype=int32)),\n",
       " (-6.414328, array([  1,  66,  11, 264,   2], dtype=int32)),\n",
       " (-7.2289867, array([  1,  66,  11, 220,   2], dtype=int32)),\n",
       " (-8.5574875, array([  1,  66,  11,  11, 220,   2], dtype=int32)),\n",
       " (-8.905851, array([  1,  11, 591,  52,  12,   2], dtype=int32)),\n",
       " (-9.104354, array([  1,  66,  11,  11, 264,   2], dtype=int32)),\n",
       " (-5.295619, array([  1,  11, 591,  52,  12,  49,   2], dtype=int32)),\n",
       " (-7.788592, array([  1,  11, 591,  52,  12, 321,   2], dtype=int32)),\n",
       " (-9.201298, array([  1,  11, 591,  52,  11, 220,   2], dtype=int32)),\n",
       " (-9.276505, array([  1,  11, 591,  52,  12,  69,   2], dtype=int32)),\n",
       " (-9.307303, array([  1,  11, 591,  52,  11, 231,   2], dtype=int32)),\n",
       " (-9.3509035, array([  1,  66,  11,  11, 619,   2], dtype=int32))]"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "beamed[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "predicted = [max(b, key = lambda t: t[0])[1] for b in beamed]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "row 1\n",
      "QUESTION: i am a werewolf\n",
      "REAL ANSWER: a werewolf\n",
      "PREDICTED ANSWER: a werewolf \n",
      "\n",
      "row 2\n",
      "QUESTION: i was dreaming again\n",
      "REAL ANSWER: i would think so\n",
      "PREDICTED ANSWER: i would think so \n",
      "\n",
      "row 3\n",
      "QUESTION: the kitchen\n",
      "REAL ANSWER: very nice\n",
      "PREDICTED ANSWER: very nice \n",
      "\n",
      "row 4\n",
      "QUESTION: the bedroom\n",
      "REAL ANSWER: there is only one bed\n",
      "PREDICTED ANSWER: there is only one bed \n",
      "\n"
     ]
    }
   ],
   "source": [
    "for i in range(len(batch_x)):\n",
    "    print('row %d'%(i+1))\n",
    "    print('QUESTION:',' '.join([rev_dictionary_from[n] for n in batch_x[i] if n not in [0,1,2,3]]))\n",
    "    print('REAL ANSWER:',' '.join([rev_dictionary_to[n] for n in batch_y[i] if n not in[0,1,2,3]]))\n",
    "    print('PREDICTED ANSWER:',' '.join([rev_dictionary_to[n] for n in predicted[i] if n not in[0,1,2,3]]),'\\n')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "row 1\n",
      "QUESTION: but david\n",
      "REAL ANSWER: is here that\n",
      "PREDICTED ANSWER: that is right \n",
      "\n",
      "row 2\n",
      "QUESTION: hopeless it is hopeless\n",
      "REAL ANSWER: tell ballet then back\n",
      "PREDICTED ANSWER: how could it not \n",
      "\n",
      "row 3\n",
      "QUESTION: miss price\n",
      "REAL ANSWER: yes learning\n",
      "PREDICTED ANSWER: yes doctor \n",
      "\n",
      "row 4\n",
      "QUESTION: mr kessler wake up please\n",
      "REAL ANSWER: is here are\n",
      "PREDICTED ANSWER: right just a sec \n",
      "\n",
      "row 5\n",
      "QUESTION: there were witnesses\n",
      "REAL ANSWER: why she out\n",
      "PREDICTED ANSWER: show me \n",
      "\n",
      "row 6\n",
      "QUESTION: what about it\n",
      "REAL ANSWER: not you are\n",
      "PREDICTED ANSWER: what is \n",
      "\n",
      "row 7\n",
      "QUESTION: go on ask them\n",
      "REAL ANSWER: i just home\n",
      "PREDICTED ANSWER: why not \n",
      "\n",
      "row 8\n",
      "QUESTION: beware the moon\n",
      "REAL ANSWER: seen hi is he\n",
      "PREDICTED ANSWER: show me \n",
      "\n",
      "row 9\n",
      "QUESTION: did you hear that\n",
      "REAL ANSWER: is down what\n",
      "PREDICTED ANSWER: then why \n",
      "\n",
      "row 10\n",
      "QUESTION: i heard that\n",
      "REAL ANSWER: it here not\n",
      "PREDICTED ANSWER: all vienna has heard that \n",
      "\n",
      "row 11\n",
      "QUESTION: the hound of the baskervilles\n",
      "REAL ANSWER: heard\n",
      "PREDICTED ANSWER: do you believe in it \n",
      "\n",
      "row 12\n",
      "QUESTION: it is moving\n",
      "REAL ANSWER: not you hear\n",
      "PREDICTED ANSWER: not bad \n",
      "\n",
      "row 13\n",
      "QUESTION: nice doggie good boy\n",
      "REAL ANSWER: bill stupid\n",
      "PREDICTED ANSWER: we bought you \n",
      "\n",
      "row 14\n",
      "QUESTION: it sounds far away\n",
      "REAL ANSWER: that pecos baby seen hi\n",
      "PREDICTED ANSWER: not likely convict \n",
      "\n",
      "row 15\n",
      "QUESTION: debbie klein cried a lot\n",
      "REAL ANSWER: is will srai not\n",
      "PREDICTED ANSWER: mrs christian \n",
      "\n",
      "row 16\n",
      "QUESTION: what are you doing here\n",
      "REAL ANSWER: is know look i\n",
      "PREDICTED ANSWER: okay she is right \n",
      "\n"
     ]
    }
   ],
   "source": [
    "batch_x, seq_x = pad_sentence_batch(X_test[:batch_size], PAD)\n",
    "batch_y, seq_y = pad_sentence_batch(Y_test[:batch_size], PAD)\n",
    "beamed = beam_search(batch_x, 5)\n",
    "predicted = [max(b, key = lambda t: t[0])[1] for b in beamed]\n",
    "\n",
    "for i in range(len(batch_x)):\n",
    "    print('row %d'%(i+1))\n",
    "    print('QUESTION:',' '.join([rev_dictionary_from[n] for n in batch_x[i] if n not in [0,1,2,3]]))\n",
    "    print('REAL ANSWER:',' '.join([rev_dictionary_to[n] for n in batch_y[i] if n not in[0,1,2,3]]))\n",
    "    print('PREDICTED ANSWER:',' '.join([rev_dictionary_to[n] for n in predicted[i] if n not in[0,1,2,3]]),'\\n')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
